/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal abstract class IndexDataSourceProviderHelper
  {
    #region Static Fields

    private static readonly Guid EmptyCallId = DataUtils.CreateCallId();
    private static readonly Guid GetDataCallId = DataUtils.CreateCallId();
    private static readonly Guid GetFixedGroupCountCallId = DataUtils.CreateCallId();
    private static readonly Guid GetGroupCountCallId = DataUtils.CreateCallId();
    private static readonly Guid GetGroupCountsCallId = DataUtils.CreateCallId();
    private static readonly Guid GetGroupCountGroupStartIndexCallId = DataUtils.CreateCallId();
    private static readonly Guid GetGroupSizeCallId = DataUtils.CreateCallId();
    private static readonly Guid GetItemsCallId = DataUtils.CreateCallId();
    private static readonly Guid GetItemsCountCallId = DataUtils.CreateCallId();
    private static readonly Guid GetSizeCallId = DataUtils.CreateCallId();
    private static readonly Guid GetStartIndexCallId = DataUtils.CreateCallId();
    private static readonly Guid GetStatValueCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal IndexDataSourceProviderHelper( IEnumerable<SortDescription> defaultSortOrder )
    {
      m_defaultSortOrder = ( defaultSortOrder != null )
                             ? defaultSortOrder.ToList()
                             : new List<SortDescription>( 0 );

      m_emptyDependentCall = new DependentCall(
        ( cb, st ) =>
        {
          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.EmptyCallId, cb, st );
          asyncResult.StartSync( null );

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.EmptyCallId );

          return new DependentCallResult( iar.GetCallResult<object>() );
        } );
    }

    #endregion

    #region CanFind Protected Property

    protected virtual bool CanFind
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region DefaultSortOrder Private Property

    private ICollection<SortDescription> DefaultSortOrder
    {
      get
      {
        return m_defaultSortOrder;
      }
    }

    private ICollection<SortDescription> m_defaultSortOrder;

    #endregion

    #region EmptyDependentCall Private Property

    private DependentCall EmptyDependentCall
    {
      get
      {
        return m_emptyDependentCall;
      }
    }

    private DependentCall m_emptyDependentCall;

    #endregion

    protected abstract bool CanSortBy( IEnumerable<SortDescription> sortOrder );

    protected abstract IAsyncResult BeginCountQuery(
      CountQueryParameters queryParameters,
      AsyncCallback callback,
      object state );
    protected abstract CountQueryResult EndCountQuery( IAsyncResult asyncResult );

    protected abstract IAsyncResult BeginDataQuery(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state );
    protected abstract DataQueryResult EndDataQuery( IAsyncResult asyncResult );

    protected virtual IAsyncResult BeginFindQuery(
      FindQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual FindQueryResult EndFindQuery( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected abstract IAsyncResult BeginGroupCountQuery(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state );

    protected abstract GroupCountQueryResult EndGroupCountQuery( IAsyncResult asyncResult );

    protected abstract IAsyncResult BeginStatQuery(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state );
    protected abstract StatQueryResult EndStatQuery( IAsyncResult asyncResult );

    internal IAsyncResult BeginGetItemsCount(
      CountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      QueryContext context = new QueryContext();
      context.IsQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      CountQueryParameters newQueryParameters = this.ConvertQueryParameters( queryParameters );

      SortRequest sortRequest = newQueryParameters.Sort;
      FilterRequest filterRequest = newQueryParameters.Filter;
      FilterGroupRequest filterGroupRequest = newQueryParameters.FilterGroup;
      JumpRequest jumpRequest = newQueryParameters.Jump;

      AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetItemsCountCallId, callback, state );

      // Retrieve the data source size only if necessary.
      if( ( context.IsQueryingForward ) || ( jumpRequest == null ) )
      {
        asyncResult.AddDependentCall(
          this.GetDataSourceSize(
            sortRequest,
            filterRequest,
            filterGroupRequest,
            context ) );
      }

      // Retrieve the index of the target if necessary.
      if( jumpRequest != null )
      {
        Debug.Assert( ( filterGroupRequest == null ) && ( this.CanFind ) );

        asyncResult.AddDependentCall(
          this.FindGroupStartIndex( jumpRequest, sortRequest, filterRequest, context ) );
      }

      // Calculate the items count from the information of the previous queries.
      asyncResult.AddDependentCall( IndexDataSourceProviderHelper.GetItemsCount( context ) );

      asyncResult.Start();

      return asyncResult;
    }

    internal CountQueryResult EndGetItemsCount( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( IndexDataSourceProviderHelper.GetItemsCountCallId );

      return asyncResult.GetCallResult<CountQueryResult>();
    }

    internal IAsyncResult BeginGetItems(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      QueryContext context = new QueryContext();
      context.IsQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      DataQueryParameters newQueryParameters = this.ConvertQueryParameters( queryParameters );
      AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetItemsCallId, callback, state );

      SortRequest sortRequest = newQueryParameters.Sort;
      FilterRequest filterRequest = newQueryParameters.Filter;
      ItemJumpRequest itemJumpRequest = newQueryParameters.ItemJump;
      JumpRequest jumpRequest = newQueryParameters.Jump;
      SkipRequest skipRequest = newQueryParameters.Skip;
      TakeRequest takeRequest = newQueryParameters.Take;

      // Retrieve the starting index and set it in the context.
      if( itemJumpRequest != null )
      {
        asyncResult.AddDependentCall(
          this.GetItemStartIndex(
            itemJumpRequest,
            sortRequest,
            filterRequest,
            context ) );
      }
      else if( jumpRequest != null )
      {
        asyncResult.AddDependentCall(
          this.FindGroupStartIndex(
            jumpRequest,
            sortRequest,
            filterRequest,
            context ) );
      }
      else
      {
        asyncResult.AddDependentCall(
          this.GetStartIndex(
            sortRequest,
            filterRequest,
            context ) );
      }

      // Retrieve the requested items.
      asyncResult.AddDependentCall( this.GetData( newQueryParameters, context ) );

      asyncResult.Start();

      return asyncResult;
    }

    internal DataQueryResult EndGetItems( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( IndexDataSourceProviderHelper.GetItemsCallId );

      return asyncResult.GetCallResult<DataQueryResult>();
    }

    internal IAsyncResult BeginGetGroupCount(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      GroupCountQueryContext context = new GroupCountQueryContext();
      context.IsQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      GroupCountQueryParameters newQueryParameters = this.ConvertQueryParameters( queryParameters );
      AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetGroupCountCallId, callback, state );

      SortRequest sortRequest = newQueryParameters.Sort;
      FilterRequest filterRequest = newQueryParameters.Filter;
      FilterGroupRequest filterGroupRequest = newQueryParameters.FilterGroup;
      JumpRequest jumpRequest = newQueryParameters.Jump;

      // Retrieve the data source size if necessary.
      if( ( context.IsQueryingForward ) || ( jumpRequest == null ) )
      {
        asyncResult.AddDependentCall(
          this.GetDataSourceSize(
            sortRequest,
            filterRequest,
            null,
            context ) );
      }

      // Retrieve the group size and it's start index.
      if( filterGroupRequest != null )
      {
        asyncResult.AddDependentCall(
          this.GetGroupSize(
            sortRequest,
            filterRequest,
            filterGroupRequest,
            context ) );

        asyncResult.AddDependentCall(
          this.FindGroupCountGroupStartIndex(
            filterGroupRequest,
            sortRequest,
            filterRequest,
            context ) );
      }

      // Retrieve the starting index.
      if( jumpRequest != null )
      {
        asyncResult.AddDependentCall(
          this.FindGroupStartIndex(
            jumpRequest,
            sortRequest,
            filterRequest,
            context ) );
      }
      else
      {
        asyncResult.AddDependentCall(
          this.GetStartIndex(
            sortRequest,
            filterRequest,
            context ) );
      }

      // Retrieve the group count.
      asyncResult.AddDependentCall(
        this.GetGroupCount(
          sortRequest,
          filterRequest,
          context ) );

      asyncResult.Start();

      return asyncResult;
    }

    internal GroupCountQueryResult EndGetGroupCount( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( IndexDataSourceProviderHelper.GetGroupCountCallId );

      return asyncResult.GetCallResult<GroupCountQueryResult>();
    }

    internal IAsyncResult BeginGetStatValue(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      StatQueryParameters newQueryParameters = this.ConvertQueryParameters( queryParameters );

      AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetStatValueCallId, callback, state );

      // Retrieve the requested items.
      asyncResult.AddDependentCall(
        ( cb, st ) => this.BeginStatQuery( newQueryParameters, cb, st ),
        ( par, iar ) => new DependentCallResult( this.EndStatQuery( iar ) ) );

      asyncResult.Start();

      return asyncResult;
    }

    internal StatQueryResult EndGetStatValue( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( IndexDataSourceProviderHelper.GetStatValueCallId );

      return asyncResult.GetCallResult<StatQueryResult>();
    }

    private DependentCall GetData( DataQueryParameters queryParameters, QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      return new DependentCall(
        ( cb, st ) =>
        {
          // Find out the number of items to skip.
          long skipCount = ( queryParameters.Skip != null ) ? queryParameters.Skip.Count : 0;

          // Since the targeted starting item was deleted, we will start next to it.
          // We need to adjust the skip counter accordingly.
          if( ( context.TargetDeleted ) && ( skipCount > 0 ) )
          {
            skipCount--;
          }

          // Find out the number of items to take.
          Nullable<int> takeCount = null;
          if( queryParameters.Take != null )
          {
            takeCount = queryParameters.Take.Count;
          }

          // Find out the starting index and the number of items to take.
          long targetIndex = context.StartIndex;
          long startIndex;
          Nullable<int> length;

          IndexDataSourceProviderHelper.GetIndexRange(
            targetIndex,
            skipCount,
            takeCount,
            context.IsQueryingForward,
            out startIndex,
            out length );

          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetDataCallId, cb, st );

          // There is no item to get if we're starting out of bound or simply
          // if no item must be taken.
          if( ( startIndex < 0 ) || ( length == 0 ) )
          {
            asyncResult.AddDependentCall( this.GetDataOutOfBound( context ) );
          }
          else
          {
            SortRequest sortRequest = queryParameters.Sort;
            FilterRequest filterRequest = queryParameters.Filter;

            // Retrieve the data source size before the items.
            asyncResult.AddDependentCall(
              this.GetDataSourceSize(
                sortRequest,
                filterRequest,
                null,
                context ) );

            // Retrieve the targeted items.
            asyncResult.AddDependentCall(
              this.GetData(
                sortRequest,
                filterRequest,
                targetIndex,
                startIndex,
                length,
                context ) );
          }

          asyncResult.Start();

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetDataCallId );

          return new DependentCallResult( iar.GetCallResult<DataQueryResult>() );
        } );
    }

    private DependentCall GetData(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      long targetIndex,
      long startIndex,
      Nullable<int> length,
      QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      return new DependentCall(
        ( cb, st ) =>
        {
          SkipRequest skipRequest = ( startIndex > 0 ) ? new SkipRequest( startIndex ) : null;
          TakeRequest takeRequest = ( length.HasValue ) ? new TakeRequest( length.Value ) : null;

          // Create the query parameters to be use by the DataSourceProvider.
          DataQueryParameters newQueryParameters = new DataQueryParameters(
                                                     takeRequest,
                                                     skipRequest,
                                                     sortRequest,
                                                     filterRequest,
                                                     null,
                                                     null );

          return this.BeginDataQuery( newQueryParameters, cb, st );
        },
        ( par, iar ) =>
        {
          DataQueryResult queryResult = this.EndDataQuery( iar );

          // Find out the total number of items in the source.
          long totalCount = queryResult.TotalCount.GetValueOrDefault( context.DataSourceSize.Value );

          // Retrieve the context for which the index will be generated.
          DataQueryContext keyContext = IndexDataSourceProviderHelper.GetCurrentContext(
                                          sortRequest,
                                          filterRequest,
                                          totalCount );

          // Retrieve the items found.
          List<IndexDataObject> dataObjects;
          if( queryResult.Data != null )
          {
            dataObjects = queryResult.Data.Cast<object>().Select(
                            ( item, offset ) => new IndexDataObject( item, startIndex + offset, keyContext ) ).ToList();
          }
          else
          {
            dataObjects = new List<IndexDataObject>( 0 );
          }

          bool isQueryingForward = context.IsQueryingForward;

          // The result must be reversed if we were querying in the opposite direction.
          if( !isQueryingForward )
          {
            dataObjects.Reverse();
          }

          // If we would have queried backward, we would have started after the
          // end of the source.
          if( ( !isQueryingForward ) && ( dataObjects.Count != length ) )
            return new DependentCallResult(
              new DataQueryResult(
                Enumerable.Empty<IndexDataObject>(),
                null,
                totalCount ) );

          Nullable<long> inlineCount = null;

          if( isQueryingForward )
          {
            // The inline count given by the external data source cannot be used explicitly
            // because the current helper may have calculated the starting index from a
            // request that does or doesn't influence the inline count.
            if( queryResult.InlineCount.HasValue )
            {
              inlineCount = queryResult.InlineCount.Value - targetIndex;
            }
            // We'll try to calculate the inline count from the total count we already have.
            else
            {
              inlineCount = totalCount - targetIndex;

              // Since our total count may no be up to date anymore, we must not return an
              // invalid inline count in the current context.
              if( ( inlineCount < 0 ) || ( inlineCount.Value + dataObjects.Count > totalCount ) )
              {
                inlineCount = null;
              }
            }
          }
          else
          {
            inlineCount = targetIndex + 1;
          }

          return new DependentCallResult( new DataQueryResult( dataObjects, inlineCount, totalCount ) );
        } );
    }

    private DependentCall GetDataOutOfBound( QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      return new DependentCall(
        ( cb, st ) =>
        {
          Nullable<long> inlineCount = null;
          Nullable<long> totalCount = context.DataSourceSize;

          if( context.IsQueryingForward )
          {
            if( totalCount.HasValue )
            {
              inlineCount = Math.Max( totalCount.Value - context.StartIndex, 0 );
            }
          }
          else
          {
            inlineCount = context.StartIndex + 1;
          }

          IEnumerable<IndexDataObject> dataObjects = Enumerable.Empty<IndexDataObject>();

          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetDataCallId, cb, st );

          asyncResult.StartSync(
            new DataQueryResult(
              Enumerable.Empty<IndexDataObject>(),
              inlineCount,
              totalCount ) );

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetDataCallId );

          return new DependentCallResult( iar.GetCallResult<DataQueryResult>() );
        } );
    }

    private DependentCall GetGroupCount(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      GroupCountQueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      return new DependentCall(
        ( cb, st ) =>
        {
          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetGroupCountsCallId, cb, st );

          long index;
          long length;

          // The previous queries we able to locate the targeted items.
          if( context.GroupExists )
          {
            bool forward = context.IsQueryingForward;

            // A FilterGroupRequest was targeting a group.
            if( context.GroupSize.HasValue )
            {
              long groupSize = context.GroupSize.Value;
              long startIndex = context.StartIndex;
              long groupStartIndex = context.GroupStartIndex;

              if( forward )
              {
                index = Math.Max( startIndex, groupStartIndex );
                length = Math.Max( 0, groupSize - ( index - groupStartIndex ) );
              }
              else
              {
                index = Math.Min( startIndex, groupStartIndex );
                length = Math.Max( 0, startIndex - groupStartIndex + 1 );
              }
            }
            // There was no FilterGroupRequest.
            else
            {
              if( forward )
              {
                index = context.StartIndex;
                length = Math.Max( 0, context.DataSourceSize.Value - index );
              }
              else
              {
                index = 0;
                length = context.StartIndex + 1;
              }
            }
          }
          else
          {
            index = 0;
            length = 0;
          }

          if( ( length <= 0 ) || ( index < 0 ) )
          {
            asyncResult.AddDependentCall( this.GetEmptyGroupCount( sortRequest ) );
          }
          else if( length == 1 )
          {
            asyncResult.AddDependentCall( this.GetUniqueGroupCount( sortRequest ) );
          }
          else
          {
            SkipRequest skipRequest = ( index > 0 ) ? new SkipRequest( index ) : null;
            TakeRequest takeRequest = new TakeRequest( Convert.ToInt32( length ) );

            asyncResult.AddDependentCall(
              this.GetGroupCount(
                sortRequest,
                filterRequest,
                skipRequest,
                takeRequest ) );
          }

          asyncResult.Start();

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetGroupCountsCallId );

          return new DependentCallResult( iar.GetCallResult<GroupCountQueryResult>() );
        } );
    }

    private DependentCall GetGroupCount(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      SkipRequest skipRequest,
      TakeRequest takeRequest )
    {
      if( sortRequest == null )
        throw new ArgumentNullException( "sortRequest" );

      if( takeRequest == null )
        throw new ArgumentNullException( "takeRequest" );

      return new DependentCall(
        ( cb, st ) =>
        {
          SkipRequest startIndex = ( ( skipRequest != null ) && ( skipRequest.Count > 0 ) )
                                     ? skipRequest
                                     : null;

          GroupCountQueryParameters queryParameters = new GroupCountQueryParameters(
                                                        sortRequest,
                                                        filterRequest,
                                                        null,
                                                        null,
                                                        startIndex,
                                                        takeRequest );

          return this.BeginGroupCountQuery( queryParameters, cb, st );
        },
        ( par, iar ) => new DependentCallResult( this.EndGroupCountQuery( iar ) ) );
    }

    private DependentCall GetEmptyGroupCount( SortRequest sortRequest )
    {
      return this.GetFixedGroupCount( sortRequest, 0 );
    }

    private DependentCall GetUniqueGroupCount( SortRequest sortRequest )
    {
      return this.GetFixedGroupCount( sortRequest, 1 );
    }

    private DependentCall GetFixedGroupCount( SortRequest sortRequest, long count )
    {
      if( sortRequest == null )
        throw new ArgumentNullException( "sortRequest" );

      return new DependentCall(
        ( cb, st ) =>
        {
          IEnumerable<long> groupCount = Enumerable.Repeat( count, sortRequest.SortDescriptions.Count ).ToList();
          GroupCountQueryResult queryResult = new GroupCountQueryResult( groupCount );

          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetFixedGroupCountCallId, cb, st );
          asyncResult.StartSync( queryResult );

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetFixedGroupCountCallId );

          return new DependentCallResult( iar.GetCallResult<GroupCountQueryResult>() );
        } );
    }

    private DependentCall GetItemStartIndex(
      ItemJumpRequest jumpRequest,
      SortRequest sortRequest,
      FilterRequest filterRequest,
      QueryContext context )
    {
      if( jumpRequest == null )
        throw new ArgumentNullException( "jumpRequest" );

      if( context == null )
        throw new ArgumentNullException( "context" );

      IndexDataItem targetItem = jumpRequest.Item as IndexDataItem;
      if( targetItem == null )
        throw new DataInternalException();

      // Make sure the index is specified.
      bool reverseOrder = IndexDataSourceProviderHelper.ValidateIndexKey( targetItem.Index, sortRequest, filterRequest );
      long startIndex = targetItem.Index.Value.Value;

      context.StartIndex = startIndex;
      context.TargetDeleted = targetItem.IsDeleted;

      return this.GetItemStartIndex( filterRequest, context, reverseOrder );
    }

    private DependentCall GetItemStartIndex(
      FilterRequest filterRequest,
      QueryContext context,
      bool isStartIndexReversed )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      // The StartIndex is good as it is.
      if( !isStartIndexReversed )
        return this.EmptyDependentCall;

      // Create the DependentCall that calculates the index based on the data source size.
      DependentCall calculateIndex =
        new DependentCall(
          ( cb, st ) =>
          {
            AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetStartIndexCallId, cb, st );

            long startIndex = Math.Max( 0, context.DataSourceSize.Value - context.StartIndex - 1 );
            context.StartIndex = startIndex;

            asyncResult.StartSync( startIndex );

            return asyncResult;
          },
          ( par, iar ) =>
          {
            iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetStartIndexCallId );

            return new DependentCallResult( null );
          } );

      return new DependentCall(
        ( cb, st ) =>
        {
          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetStartIndexCallId, cb, st );

          asyncResult.AddDependentCall( this.GetDataSourceSize( null, filterRequest, null, context ) );
          asyncResult.AddDependentCall( calculateIndex );
          asyncResult.Start();

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetStartIndexCallId );

          return new DependentCallResult( null );
        } );
    }

    private DependentCall GetStartIndex(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      context.TargetDeleted = false;

      // Start at index 0.
      if( context.IsQueryingForward )
      {
        context.StartIndex = 0;

        return this.EmptyDependentCall;
      }

      // Create the DependentCall that will return a StartIndex of data source size - 1.
      return new DependentCall(
        ( cb, st ) =>
        {
          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetStartIndexCallId, cb, st );

          asyncResult.AddDependentCall( this.GetDataSourceSize( sortRequest, filterRequest, null, context ) );
          asyncResult.Start();

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetStartIndexCallId );

          context.StartIndex = context.DataSourceSize.Value - 1;

          return new DependentCallResult( null );
        } );
    }

    private DependentCall FindGroupStartIndex(
      JumpRequest jumpRequest,
      SortRequest sortRequest,
      FilterRequest filterRequest,
      QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      if( jumpRequest == null )
        throw new ArgumentNullException( "jumpRequest" );

      if( sortRequest == null )
        throw new ArgumentNullException( "sortRequest" );

      if( !this.CanFind )
        throw new DataInternalException();

      return new DependentCall(
        ( cb, st ) =>
        {
          // Convert the JumpRequest into a FindRequest.
          IEnumerable<FindValue> findValues = ( from jumpValue in jumpRequest.Values.Take( sortRequest.SortDescriptions.Count )
                                                select new FindValue( jumpValue.Value ) );
          FindPosition findPosition = ( jumpRequest.StartPosition == JumpStartPosition.Before )
                                        ? FindPosition.First
                                        : FindPosition.Last;
          FindRequest findRequest = new FindRequest( findValues, findPosition );

          FindQueryParameters queryParameters = new FindQueryParameters( sortRequest, filterRequest, findRequest );

          return this.BeginFindQuery( queryParameters, cb, st );
        },
        ( par, iar ) =>
        {
          FindQueryResult queryResult = this.EndFindQuery( iar );
          FindResult result = queryResult.Result;
          long index = result.Index;

          // Adjust the index if necessary since the find operation returned the index of the target
          // position while the jump ask for the position before or after a given target.
          if( result.IsMatch )
          {
            if( jumpRequest.StartPosition == JumpStartPosition.After )
            {
              if( context.IsQueryingForward )
              {
                index++;
              }
              else
              {
                index--;
              }
            }
          }

          context.StartIndex = index;
          context.TargetDeleted = !result.IsMatch;

          return new DependentCallResult( index );
        } );
    }

    private DependentCall FindGroupCountGroupStartIndex(
      FilterGroupRequest filterGroupRequest,
      SortRequest sortRequest,
      FilterRequest filterRequest,
      GroupCountQueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      if( filterGroupRequest == null )
        throw new ArgumentNullException( "filterGroupRequest" );

      if( sortRequest == null )
        throw new ArgumentNullException( "sortRequest" );

      if( !this.CanFind )
        throw new DataInternalException();

      return new DependentCall(
        ( cb, st ) =>
        {
          // The target group doesn't exist.  There is no need to start a query to find it.
          if( !context.GroupExists )
          {
            AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetGroupCountGroupStartIndexCallId, cb, st );
            asyncResult.AddDependentCall( this.EmptyDependentCall );
            asyncResult.Start();

            return asyncResult;
          }

          // Create a query to find the start index of the target group.
          IEnumerable<FindValue> findValues = ( from filterValue in filterGroupRequest.Values.Take( sortRequest.SortDescriptions.Count )
                                                select new FindValue( filterValue.Value ) );
          FindRequest findRequest = new FindRequest( findValues, FindPosition.First );
          FindQueryParameters queryParameters = new FindQueryParameters( sortRequest, filterRequest, findRequest );

          return this.BeginFindQuery( queryParameters, cb, st );
        },
        ( par, iar ) =>
        {
          if( !context.GroupExists )
          {
            iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetGroupCountGroupStartIndexCallId );
          }
          else
          {
            FindQueryResult queryResult = this.EndFindQuery( iar );
            FindResult result = queryResult.Result;

            context.GroupExists = result.IsMatch;
            context.GroupStartIndex = result.Index;
          }

          return new DependentCallResult( null );
        } );
    }

    private DependentCall GetDataSourceSize(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      FilterGroupRequest filterGroupRequest,
      QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      Func<Nullable<long>> sizeGetter = () => context.DataSourceSize;
      Action<long> sizeSetter = ( size ) => context.DataSourceSize = size;

      return this.GetSize(
        sortRequest,
        filterRequest,
        filterGroupRequest,
        sizeGetter,
        sizeSetter );
    }

    private DependentCall GetGroupSize(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      FilterGroupRequest filterGroupRequest,
      GroupCountQueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      Func<Nullable<long>> sizeGetter = () => context.GroupSize;
      Action<long> sizeSetter =
        ( size ) =>
        {
          context.GroupSize = size;
          context.GroupExists = ( size > 0 );
        };

      return this.GetSize(
        sortRequest,
        filterRequest,
        filterGroupRequest,
        sizeGetter,
        sizeSetter );
    }

    private DependentCall GetSize(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      FilterGroupRequest filterGroupRequest,
      Func<Nullable<long>> sizeGetter,
      Action<long> sizeSetter )
    {
      if( sizeGetter == null )
        throw new ArgumentNullException( "sizeGetter" );

      if( sizeSetter == null )
        throw new ArgumentNullException( "sizeSetter" );

      Nullable<long> currentSize = sizeGetter.Invoke();

      // We already have the size.
      if( currentSize.HasValue )
        return new DependentCall(
          ( cb, st ) =>
          {
            AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetSizeCallId, cb, st );
            asyncResult.StartSync( currentSize.Value );

            return asyncResult;
          },
          ( par, iar ) =>
          {
            iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetSizeCallId );

            return new DependentCallResult( null );
          } );

      // Find out the target size.
      return new DependentCall(
        ( cb, st ) =>
        {
          CountQueryParameters queryParameters;

          if( filterGroupRequest == null )
          {
            queryParameters = new CountQueryParameters( null, filterRequest, null, null );
          }
          else
          {
            queryParameters = new CountQueryParameters( sortRequest, filterRequest, null, filterGroupRequest );
          }

          return this.BeginCountQuery( queryParameters, cb, st );
        },
        ( par, iar ) =>
        {
          CountQueryResult queryResult = this.EndCountQuery( iar );
          long count = queryResult.Count;

          sizeSetter.Invoke( count );

          return new DependentCallResult( null );
        } );
    }

    private CountQueryParameters ConvertQueryParameters( CountQueryParameters queryParameters )
    {
      // Make sure the filter isn't referencing the IndexDataItem.IndexKeyName field.
      if( IndexDataSourceProviderHelper.IsUsingIndexKey( queryParameters.Filter ) )
        // The filter references the index field, which isn't supported.
        throw new DataInternalException();

      bool isQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      return new CountQueryParameters(
        this.ConvertToDefaultSortRequest( queryParameters.Sort, isQueryingForward ),
        queryParameters.Filter,
        this.ConvertToDefaultJumpRequest( queryParameters.Jump, isQueryingForward ),
        queryParameters.FilterGroup );
    }

    private DataQueryParameters ConvertQueryParameters( DataQueryParameters queryParameters )
    {
      // Make sure the filter isn't referencing the IndexDataItem.IndexKeyName field.
      if( IndexDataSourceProviderHelper.IsUsingIndexKey( queryParameters.Filter ) )
        // The filter references the index field, which isn't supported.
        throw new DataInternalException();

      bool isQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      return new DataQueryParameters(
        queryParameters.Take,
        queryParameters.Skip,
        this.ConvertToDefaultSortRequest( queryParameters.Sort, isQueryingForward ),
        queryParameters.Filter,
        queryParameters.ItemJump,
        this.ConvertToDefaultJumpRequest( queryParameters.Jump, isQueryingForward ) );
    }

    private GroupCountQueryParameters ConvertQueryParameters( GroupCountQueryParameters queryParameters )
    {
      // Make sure the filter isn't referencing the IndexDataItem.IndexKeyName field.
      if( IndexDataSourceProviderHelper.IsUsingIndexKey( queryParameters.Filter ) )
        // The filter references the index field, which isn't supported.
        throw new DataInternalException();

      Debug.Assert( ( queryParameters.Skip == null )
                 && ( queryParameters.Take == null ) );

      bool isQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      return new GroupCountQueryParameters(
        this.ConvertToDefaultSortRequest( queryParameters.Sort, isQueryingForward ),
        queryParameters.Filter,
        this.ConvertToDefaultJumpRequest( queryParameters.Jump, isQueryingForward ),
        queryParameters.FilterGroup,
        null,
        null );
    }

    private StatQueryParameters ConvertQueryParameters( StatQueryParameters queryParameters )
    {
      // Make sure the filter isn't referencing the IndexDataItem.IndexKeyName field.
      if( IndexDataSourceProviderHelper.IsUsingIndexKey( queryParameters.Filter ) )
        // The filter references the index field, which isn't supported.
        throw new DataInternalException();

      bool isQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      return new StatQueryParameters(
        queryParameters.Function,
        this.ConvertToDefaultSortRequest( queryParameters.Sort, isQueryingForward ),
        queryParameters.Filter,
        queryParameters.FilterGroup );
    }

    private SortRequest ConvertToDefaultSortRequest( SortRequest sortRequest, bool isQueryingForward )
    {
      List<SortDescription> newSortOrder = new List<SortDescription>();

      if( sortRequest != null )
      {
        foreach( SortDescription sortOrder in sortRequest.SortDescriptions )
        {
          if( sortOrder.PropertyName == IndexDataItem.IndexKeyName )
          {
            newSortOrder.AddRange( m_defaultSortOrder );
          }
          else if( isQueryingForward )
          {
            newSortOrder.Add( sortOrder );
          }
          else
          {
            newSortOrder.Add( new SortDescription(
                                sortOrder.PropertyName,
                                ( sortOrder.Direction == ListSortDirection.Ascending )
                                  ? ListSortDirection.Descending
                                  : ListSortDirection.Ascending ) );
          }
        }
      }

      if( newSortOrder.Count <= 0 )
        return null;

      return new SortRequest( newSortOrder );
    }

    private JumpRequest ConvertToDefaultJumpRequest( JumpRequest jumpRequest, bool isQueryingForward )
    {
      if( ( jumpRequest == null ) || ( isQueryingForward ) )
        return jumpRequest;

      JumpStartPosition startPosition = ( jumpRequest.StartPosition == JumpStartPosition.After )
                                          ? JumpStartPosition.Before
                                          : JumpStartPosition.After;

      return new JumpRequest( jumpRequest.Values, startPosition );
    }

    private bool IsQueryingForward( SortRequest sortRequest )
    {
      if( ( sortRequest == null ) || ( sortRequest.SortDescriptions.Count <= 0 ) )
        return true;

      IEnumerable<SortDescription> sortBy = sortRequest.SortDescriptions;

      foreach( SortDescription sortOrder in sortBy )
      {
        if( sortOrder.PropertyName == IndexDataItem.IndexKeyName )
          return ( sortOrder.Direction == ListSortDirection.Ascending );
      }

      // If the sort isn't using the index, we'll use it as is.
      if( this.CanSortBy( sortBy ) )
        return true;

      IEnumerable<SortDescription> reversedOrder = ( from sort in sortBy
                                                     let direction = ( sort.Direction == ListSortDirection.Ascending )
                                                                       ? ListSortDirection.Descending
                                                                       : ListSortDirection.Ascending
                                                     select new SortDescription( sort.PropertyName, direction ) ).ToList();

      // If the sort is usable in reversed order, use it.
      if( this.CanSortBy( reversedOrder ) )
        return false;

      throw new DataInternalException();
    }

    private static DependentCall GetItemsCount( QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      return new DependentCall(
        ( cb, st ) =>
        {
          AsyncResult ar = new AsyncResult( IndexDataSourceProviderHelper.GetItemsCountCallId, cb, st );

          // The StartIndex is set to 0 if the JumpRequest is null.
          long startIndex = context.StartIndex;
          long itemsCount;

          if( context.IsQueryingForward )
          {
            long dataSourceSize = context.DataSourceSize.Value;

            itemsCount = Math.Max( 0, dataSourceSize - startIndex );
          }
          else
          {
            itemsCount = Math.Max( 0, startIndex + 1 );
          }

          ar.StartSync( new CountQueryResult( itemsCount ) );

          return ar;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetItemsCountCallId );

          return new DependentCallResult( iar.GetCallResult<CountQueryResult>() );
        } );
    }

    private static void GetIndexRange(
      long targetIndex,
      long skipCount,
      Nullable<int> takeCount,
      bool forward,
      out long startIndex,
      out Nullable<int> length )
    {
      startIndex = targetIndex + ( ( forward ) ? skipCount : -skipCount );
      length = takeCount;

      // Since the target index if before the beginning of the source, there
      // is no item to get.
      if( startIndex < 0 )
      {
        length = 0;
      }

      if( forward || ( length == 0 ) || ( startIndex < 0 ) )
        return;

      long endIndex = startIndex;

      if( takeCount.HasValue )
      {
        startIndex = Math.Max( startIndex - takeCount.Value + 1, 0 );
      }
      else
      {
        startIndex = 0;
      }

      length = Convert.ToInt32( endIndex - startIndex + 1 );
    }

    private static DataQueryContext GetCurrentContext(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      long dataSourceSize )
    {
      IEnumerable<SortDescription> sortOrder = ( sortRequest != null )
                                                 ? sortRequest.SortDescriptions
                                                 : Enumerable.Empty<SortDescription>();
      Predicate<object> filterPredicate = null;
      FilterExpressionBase filterExpression = null;

      if( filterRequest != null )
      {
        filterPredicate = filterRequest.Filter;
        filterExpression = filterRequest.Expression;
      }

      return new DataQueryContext( sortOrder, filterPredicate, filterExpression, dataSourceSize );
    }

    private static bool ValidateIndexKey(
      IndexKey key,
      SortRequest sortRequest,
      FilterRequest filterRequest )
    {
      bool reverse;

      if( !IndexDataSourceProviderHelper.IsIndexKeyValid( key, sortRequest, filterRequest, out reverse ) )
        throw new DataInternalException();

      return reverse;
    }

    private static bool IsIndexKeyValid(
      IndexKey key,
      SortRequest sortRequest,
      FilterRequest filterRequest,
      out bool reverse )
    {
      reverse = false;

      if( ( key == null )
        || ( key.Context == null )
        || ( !key.Value.HasValue ) )
        return false;

      // Make sure the key is use in the same context except for the sorting order.
      DataQueryContext context = key.Context;
      bool isUsableContext = ( context != DataQueryContext.None );

      // Make sure the key has been created in the same filtering context.
      isUsableContext &= ( ( filterRequest == null ) && ( context.FilterPredicate == null ) && ( context.FilterExpression == null ) )
                      || ( ( filterRequest != null ) && ( context.FilterPredicate == filterRequest.Filter ) && ( context.FilterExpression == filterRequest.Expression ) );

      // Make sure the key has been created in the same sorting context.
      // A reverse sort order is considered valid because it has no impact on the
      // index value.
      isUsableContext &= ( ( sortRequest == null ) && ( context.SortDescriptions.Count <= 0 ) )
                      || ( ( sortRequest != null ) && ( context.SortDescriptions.Count == sortRequest.SortDescriptions.Count ) );

      if( ( isUsableContext ) && ( sortRequest != null ) )
      {
        int fieldsCount = sortRequest.SortDescriptions.Count;
        Nullable<bool> reverseOrder = null;

        // Make sure the the index key sorting context and the current sorting context
        // share the same sorting fields and are either in the same order or in
        // reverse order.
        for( int i = 0; ( isUsableContext ) && ( i < fieldsCount ); i++ )
        {
          if( sortRequest.SortDescriptions[ i ].PropertyName == context.SortDescriptions[ i ].PropertyName )
          {
            bool isReversed = ( sortRequest.SortDescriptions[ i ].Direction != context.SortDescriptions[ i ].Direction );
            if( reverseOrder.HasValue )
            {
              isUsableContext = ( reverseOrder.Value == isReversed );
            }
            else
            {
              reverseOrder = isReversed;
            }
          }
          else
          {
            isUsableContext = false;
          }
        }

        if( isUsableContext )
        {
          reverse = reverseOrder.GetValueOrDefault( false );
        }
      }

      return isUsableContext;
    }

    private static bool IsUsingIndexKey( FilterRequest filterRequest )
    {
      if( ( filterRequest == null ) || ( filterRequest.Expression == null ) )
        return false;

      IndexKeyUsageVisitor verifier = new IndexKeyUsageVisitor( filterRequest.Expression );

      return verifier.Result;
    }

    #region Private QueryContext Nested Type

    private class QueryContext
    {
      internal QueryContext()
      {
        this.IsQueryingForward = true;
      }

      internal Nullable<long> DataSourceSize
      {
        get;
        set;
      }

      internal long StartIndex
      {
        get;
        set;
      }

      internal bool TargetDeleted
      {
        get;
        set;
      }

      internal bool IsQueryingForward
      {
        get;
        set;
      }
    }

    #endregion

    #region Private GroupCountQueryContext Nested Type

    private class GroupCountQueryContext : QueryContext
    {
      internal GroupCountQueryContext()
      {
        this.GroupExists = true;
      }

      internal Nullable<long> GroupSize
      {
        get;
        set;
      }

      internal long GroupStartIndex
      {
        get;
        set;
      }

      internal bool GroupExists
      {
        get;
        set;
      }
    }

    #endregion

    #region Private IndexKeyUsageVisitor Nested Type

    private class IndexKeyUsageVisitor : FilterExpressionVisitor
    {
      internal IndexKeyUsageVisitor( FilterExpressionBase expression )
        : base( expression )
      {
      }

      internal bool Result
      {
        get
        {
          if( !m_hasRun )
          {
            this.Visit();
            m_hasRun = true;
          }

          return m_result;
        }
      }

      protected override void Initialize()
      {
        m_result = false;

        base.Initialize();
      }

      protected internal override void Visit( FilterExpression expression )
      {
        m_result |= ( expression.MemberName == IndexDataItem.IndexKeyName );
      }

      protected internal override void Visit( AndFilterExpression expression )
      {
        this.Visit( ( FilterExpressionOperator )expression );
      }

      protected internal override void Visit( OrFilterExpression expression )
      {
        this.Visit( ( FilterExpressionOperator )expression );
      }

      protected internal override void Visit( NotFilterExpression expression )
      {
        if( !m_result )
        {
          this.Visit( expression.ChildExpression );
        }
      }

      private void Visit( FilterExpressionOperator expression )
      {
        if( !m_result )
        {
          foreach( FilterExpressionBase childExpression in expression.ChildExpressions )
          {
            if( m_result )
              return;

            this.Visit( childExpression );
          }
        }
      }

      private bool m_result;
      private bool m_hasRun;
    }

    #endregion
  }
}
